<!DOCTYPE html>
<html lang="zh-Hans">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 6.2.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/vhuichen.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/vhuichen.ico">
  <link rel="icon" type="image/png" sizes="16x16" href="/vhuichen.ico">
  <link rel="mask-icon" href="/vhuichen.ico" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha256-AbA177XfpSnFEvgpYu1jMygiLabzPCJCRIBtR5jGc0k=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"vhuichen.github.io","root":"/","images":"/images","scheme":"Pisces","darkmode":false,"version":"8.13.0","exturl":false,"sidebar":{"position":"left","display":"display","padding":18,"offset":12},"copycode":{"enable":false,"style":null},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"Searching...","empty":"We didn't find any results for the search: ${query}","hits_time":"${hits} results found in ${time} ms","hits":"${hits} results found"}}</script><script src="/js/config.js"></script>

    <meta property="og:type" content="website">
<meta property="og:title" content="vhuichen">
<meta property="og:url" content="https://vhuichen.github.io/page/5/index.html">
<meta property="og:site_name" content="vhuichen">
<meta property="og:locale">
<meta property="article:author" content="vhuichen">
<meta name="twitter:card" content="summary">


<link rel="canonical" href="https://vhuichen.github.io/page/5/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":true,"isPost":false,"lang":"zh-Hans","comments":"","permalink":"","path":"page/5/index.html","title":""}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>vhuichen</title>
  






  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="Toggle navigation bar" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">vhuichen</h1>
      <i class="logo-line"></i>
    </a>
      <p class="site-subtitle" itemprop="description">记录、分享</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/" rel="section">Home</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section">Archives</a></li><li class="menu-item menu-item-about"><a href="/about/" rel="section">About</a></li>
  </ul>
</nav>




</div>
        
  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>

  <aside class="sidebar">

    <div class="sidebar-inner sidebar-overview-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          Table of Contents
        </li>
        <li class="sidebar-nav-overview">
          Overview
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="vhuichen"
      src="/images/vhuichen.jpg">
  <p class="site-author-name" itemprop="name">vhuichen</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap site-overview-item animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">78</span>
          <span class="site-state-item-name">posts</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">11</span>
        <span class="site-state-item-name">categories</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">86</span>
        <span class="site-state-item-name">tags</span></a>
      </div>
  </nav>
</div>



        </div>
      </div>
    </div>
  </aside>
  <div class="sidebar-dimmer"></div>


    </header>

    
  <div class="back-to-top" role="button" aria-label="Back to top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner index posts-expand">

    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E4%B8%83%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(2)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E4%B8%83%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(2)/" class="post-title-link" itemprop="url">《Effective Objective-C 2.0》第七章阅读笔记(2)</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-29 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-29T00:00:00+08:00">2018-05-29</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:21:50" itemprop="dateModified" datetime="2018-07-01T16:21:50+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="第七章：系统框架-2"><a href="#第七章：系统框架-2" class="headerlink" title="第七章：系统框架(2)"></a><center>第七章：系统框架(2)</center></h1><h2 id="第50条：构建缓存时选用-NSCache-而非-NSDictionary"><a href="#第50条：构建缓存时选用-NSCache-而非-NSDictionary" class="headerlink" title="第50条：构建缓存时选用 NSCache 而非 NSDictionary"></a>第50条：构建缓存时选用 NSCache 而非 NSDictionary</h2><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><p>1、当系统资源耗尽时，NSCache 可以自动删减缓存，而且还会优先删除最久没有使用的缓存。<br>2、NSCache 并不会“拷贝”键，而是“保留”它。不拷贝键的原因是：很多时候，键都是由不支持拷贝操作的对象充当的。<br>3、NSCache 是线程安全的。<br>4、可以操控缓存删减其内容的时机，有两个与系统资源相关的尺度可供调整，其一是缓存中的对象总数，其二是所有对象的“总开销”（overroll cost）。  </p>
<p>下面代码演示缓存的用法：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Network fetcher class</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">void</span>(^EOCNetworkFercherCompletionHandler)(<span class="built_in">NSData</span> *data);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">EOCNetworkFetcher</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="type">id</span>)initWithURL:(<span class="built_in">NSURL</span> *)url;  </span><br><span class="line">- (<span class="type">void</span>)startWithCompletionHandler:(EOCNetworkFercherCompletionHandler)handler;  </span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line">  </span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">EOCClass</span> </span>&#123;  </span><br><span class="line">    <span class="built_in">NSCache</span> *_cache;  </span><br><span class="line">&#125;</span><br><span class="line">  </span><br><span class="line">- (<span class="type">id</span>)init &#123;</span><br><span class="line">    <span class="keyword">self</span> = [<span class="variable language_">super</span> init];  </span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">self</span>) &#123;  </span><br><span class="line">        _cache = [<span class="built_in">NSCache</span> new];</span><br><span class="line">        <span class="comment">// 最多缓存 100 条数据</span></span><br><span class="line">        _cache.countLimit = <span class="number">100</span>;</span><br><span class="line">        <span class="comment">// 最大缓存空间 5MB</span></span><br><span class="line">        _cache.totalCostLimit = <span class="number">5</span> * <span class="number">1024</span> * <span class="number">1024</span>;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">self</span>;  </span><br><span class="line">&#125;</span><br><span class="line">  </span><br><span class="line">- (<span class="type">void</span>)downloadDataForURL:(<span class="built_in">NSURL</span> *)url &#123;  </span><br><span class="line">    <span class="built_in">NSData</span> *cachedData = [_cache objectForKey:url];  </span><br><span class="line">    <span class="keyword">if</span> (cachedData) &#123;  </span><br><span class="line">        <span class="comment">// Cache hit</span></span><br><span class="line">        [<span class="keyword">self</span> useData:cachedData];  </span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// Cache miss</span></span><br><span class="line">        EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];  </span><br><span class="line">        [fetcher startWithCompletionHandler:^(<span class="built_in">NSData</span> *data) &#123;  </span><br><span class="line">            [_cache setObject:data forKey:url cost:data.length];  </span><br><span class="line">            [<span class="keyword">self</span> useData:cachedData];</span><br><span class="line">        &#125;];  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br><span class="line"><span class="keyword">@end</span>  </span><br></pre></td></tr></table></figure>

<h4 id="NSPurgeableData"><a href="#NSPurgeableData" class="headerlink" title="NSPurgeableData"></a>NSPurgeableData</h4><p>NSPurgeableData 和 NSCache 搭配起来用，效果很好。此类是 NSMutableData 的子类，而且实现了 NSDiscardableContent 协议。如果某个对象所占有的内存能够根据需要随时丢弃，那么就可以实现该协议所定义的接口。当系统资源紧张时可以把保存 NSPurgeableData 对象的那块内存释放掉。NSDiscardableContent 协议定义了名为 isContentDiscarded 的方法，用来查询相关内存是否已释放。<br>如果需要访问某个 NSPurgeableData 对象，可以调用 beginContentAccess 方法，告诉它现在还不应该丢弃自己所占据的内存。用完之后，调用 endContentAccess 方法，告诉它在必要时可以丢弃自己所占据的内存了。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)downloadDataForURLTwo:(<span class="built_in">NSURL</span> *)url &#123;</span><br><span class="line">	<span class="built_in">NSPurgeableData</span> *cachedData = [_cache objectForKey:url];</span><br><span class="line">	<span class="keyword">if</span> (cachedData) &#123;</span><br><span class="line">		[cachedData beginContentAccess];</span><br><span class="line">		[<span class="keyword">self</span> useData:cacheData];</span><br><span class="line">		[cachedData endContentAccess];</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];</span><br><span class="line">		[fetcher startWithCompletionHandler:^(<span class="built_in">NSData</span> *data) &#123;</span><br><span class="line">			<span class="built_in">NSPurgeableData</span> *purgeableData = [<span class="built_in">NSPurgeableData</span> dataWithData:data];</span><br><span class="line">			[_cache setObject:purgeableData forKey:url cost:purgeableData.length];</span><br><span class="line">			[<span class="keyword">self</span> useData:purgeableData];</span><br><span class="line">			[purgeableData endContentAccess];</span><br><span class="line">		&#125;];</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>创建好 NSPurgeableData 后，其 “purge 引用计数”会多1，所以无需再调用 beginContentAccess 了，但使用完后必须调用 endContentAccess 方法，将多出来的 “1” 抵消掉。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>合理的使用 NSCache 可以提高程序的响应速度。</p>
<h2 id="第51条：精简-initialize-和-load-的实现代码"><a href="#第51条：精简-initialize-和-load-的实现代码" class="headerlink" title="第51条：精简 initialize 和 load 的实现代码"></a>第51条：精简 initialize 和 load 的实现代码</h2><p>有时候，类必须先执行某些初始化操作才能正常使用。在 Objective-C 中，绝大多数的类都继承自 NSObject 这个根类，该类有两个方法，可用来实现这种初始化操作。</p>
<h3 id="load"><a href="#load" class="headerlink" title="load"></a>load</h3><p>对于加入运行期系统中的每个类（class）及分类（category）来说，必定会调用此方法，而且仅调用一次。如果分类和其所属的类都定义了 load 方法，则先调用类里的，再调用分类的。  </p>
<p>执行 load 方法时，运行期系统处于“脆弱状态”（fragile state）。在执行子类的 load 方法之前，必定会先执行所有父类的 load 方法，而如果代码还依赖其他程序，那么程序库里相关类的 load 方法也必定会先执行。然而，根据某个给定的程序库，却无法判断出其中各个类的载入顺序。因此，在 load 方法中使用其他类是不安全的。  </p>
<p>load 方法不像普通方法那样，它不遵从那套继承规则。如果某个类本身没实现 load 方法，那么不管其各级父类是否实现此方法，系统都不会调用。此外，分类的其所属的类里，都可能出现 load 方法。此时两种实现代码都会调用，类的实现要比分类的实现先执行。</p>
<p>load 方法务必实现得精简一些，也就是要尽量减少其所执行操作，因为整个程序在执行 load 方法的时候都会阻塞。如果 load 方法中包含繁杂的代码，那么应用程序在执行期行就会变得无响应。也不要写等待锁，也不要调用可能会加锁的方法。</p>
<h3 id="initialize"><a href="#initialize" class="headerlink" title="initialize"></a>initialize</h3><p>只有在第一次给该类发送消息之前会调用 initialize 方法。</p>
<p>与 load 方法不同，运行系统在执行 initialize 方法时，是处于正常状态的。因此，从运行期系统完整角度上来讲，此时也可以安全使用并调用任意类中的任意方法。而且，运行期系统也能确保 initialize 方法在“线程安全的环境”中执行。这就是说，只有执行 initialize 的那个线程可以操作类或类实例。其他线程都要先阻塞，等着 initialize 执行完。</p>
<p>跟其他方法一样，如果某个类未实现 initialize 方法，而父类实现了，那么就会运行父类的代码。initialize 遵循通常的继承规则。所以应该在 initialize 方法中判断是否是当前类，代码如下：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">+ (<span class="type">void</span>)initialize &#123;</span><br><span class="line">	<span class="keyword">if</span>(<span class="keyword">self</span> == [EOCBaseClass <span class="keyword">class</span>]) &#123;</span><br><span class="line">		<span class="comment">// doSomething</span></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后，initialize 和 load 一样，都应该实现的精简一些。可以用来初始化一些全局变量，</p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p>之前写的文章 <a href="https://vhuichen.github.io/2018/04/10/180410-iOS%E5%BC%80%E5%8F%91%E4%B9%8B%E7%90%86%E8%A7%A3load%E5%92%8Cinitialize/">iOS开发之理解load和initialize</a></p>
<h2 id="第52条：别忘了-NSTimer-会保留其目标对象"><a href="#第52条：别忘了-NSTimer-会保留其目标对象" class="headerlink" title="第52条：别忘了 NSTimer 会保留其目标对象"></a>第52条：别忘了 NSTimer 会保留其目标对象</h2><p>计时器要和“运行循环”（runloop）相关联，运行循环到时候会触发任务。创建 NSTimer 时，可以将其“预先安排”在当前的运行循环中，也可以先创建好，然后由开发者来调度。无论采用哪种方式，只有把计时器放在运行循环里，它才能正常触发任务。</p>
<p>使用 NSTimer 很容易会造成引用循环。看看下面的例子  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">EOCClass</span> : <span class="title">NSObject</span></span></span><br><span class="line">- (<span class="type">void</span>)startPolling;</span><br><span class="line">- (<span class="type">void</span>)stopPolling;</span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// -- </span></span><br><span class="line"></span><br><span class="line"><span class="meta">#import <span class="string">&quot;EOCClass.h&quot;</span> </span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">EOCClass</span> </span>&#123; </span><br><span class="line">	<span class="built_in">NSTimer</span> *_pollTimer; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line">- (<span class="type">id</span>)init &#123; </span><br><span class="line">	<span class="keyword">return</span> [<span class="variable language_">super</span> init]; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)dealloc &#123; </span><br><span class="line">	[_pollTimer invalidate]; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)stopPolling &#123; </span><br><span class="line">	[_pollTimer invalidate]; </span><br><span class="line">	_pollTimer = <span class="literal">nil</span>;</span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)startPolling &#123; </span><br><span class="line">	_pollTimer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:<span class="number">5.0</span> target:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(p_doPoll) userInfo:<span class="literal">nil</span> repeats:<span class="literal">YES</span>]; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)p_doPoll &#123; </span><br><span class="line">	<span class="comment">// Poll the resource </span></span><br><span class="line">&#125; </span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>上面代码中 self 强引用了 _pollTimer ，而 _pollTimer 也强引用了 self 。所以就造成了引用循环。除非手动调用 stopPolling 这个方法，否则就会出现内存泄漏。但我们无法保证开发者一定会调用这个方法。</p>
<p>解决方法：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&lt;Foundation/Foundation.h&gt; </span></span></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">NSTimer</span> (<span class="title">EOCBlocksSupport</span>) </span></span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)eoc_timerScheduledTimerWithTimeInterval:(<span class="built_in">NSTimeInterval</span>)interval block:(<span class="type">void</span>(^)())block repeats:(<span class="type">BOOL</span>)repeats;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// --</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#import <span class="string">&quot;NSTimer+EOCBlocksSupport.h&quot;</span> </span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">NSTimer</span> (<span class="title">EOCBlocksSupport</span>) </span></span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)eoc_timerScheduledTimerWithTimeInterval:(<span class="built_in">NSTimeInterval</span>)interval block:(<span class="type">void</span>(^)())block repeats:(<span class="type">BOOL</span>)repeats &#123; </span><br><span class="line">	<span class="keyword">return</span> [<span class="keyword">self</span> scheduledTimerWithTimeInterval:interval target:<span class="keyword">self</span> selector:<span class="keyword">@selector</span>(eoc_blockInvoke:) userInfo:[block <span class="keyword">copy</span>] repeats:repeats]; </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="type">void</span>)eoc_blockInvoke:(<span class="built_in">NSTimer</span> *)timer &#123; </span><br><span class="line">	<span class="type">void</span> (^block) () = timer.userInfo; </span><br><span class="line">	block ? block() : <span class="literal">nil</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)startPolling &#123;</span><br><span class="line">	__<span class="keyword">weak</span> EOCClass *weakSelf = <span class="keyword">self</span>; </span><br><span class="line">	_pollTimer = [<span class="built_in">NSTimer</span> eoc_timerScheduledTimerWithTimeInterval:<span class="number">5.0</span> block:^&#123; </span><br><span class="line">		EOCClass *strongSelf = weakSelf; </span><br><span class="line">		[strongSelf p_doPoll]; </span><br><span class="line">	&#125; repeats:<span class="literal">YES</span>]; </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)p_doPoll &#123; </span><br><span class="line">	<span class="comment">// Poll the resource </span></span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>使用这种方法捕获到 weakSelf ，这样 self 就可以正常释放了，self 释放后， weakSelf 也就变为 nil 。从而打破了引用循环。</p>
<h3 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h3><p>在项目中我使用另一种方法也可以用来解决这个问题，代码如下：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">void</span> (^VCHTimerHandler)(<span class="type">id</span> userInfo);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">VCHWeakTimer</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)scheduledTimerWithTimeInterval:(<span class="built_in">NSTimeInterval</span>)interval</span><br><span class="line">                                     target:(<span class="type">id</span>)aTarget</span><br><span class="line">                                   selector:(SEL)aSelector</span><br><span class="line">                                   userInfo:(<span class="type">id</span>)userInfo</span><br><span class="line">                                    repeats:(<span class="type">BOOL</span>)repeats;</span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)scheduledTimerWithTimeInterval:(<span class="built_in">NSTimeInterval</span>)interval</span><br><span class="line">                                      block:(VCHTimerHandler)block</span><br><span class="line">                                   userInfo:(<span class="type">id</span>)userInfo</span><br><span class="line">                                    repeats:(<span class="type">BOOL</span>)repeats;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&quot;VCHWeakTimer.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">VCHWeakTimer</span>()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">weak</span>) <span class="type">id</span> target;</span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) SEL selector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">VCHWeakTimer</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)fire:(<span class="type">id</span>)obj &#123;</span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> clang diagnostic push</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> clang diagnostic ignored <span class="string">&quot;-Warc-performSelector-leaks&quot;</span></span></span><br><span class="line">    [<span class="keyword">self</span>.target performSelector:<span class="keyword">self</span>.selector withObject:obj];</span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> clang diagnostic pop</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)scheduledTimerWithTimeInterval:(<span class="built_in">NSTimeInterval</span>)interval</span><br><span class="line">                                     target:(<span class="type">id</span>)aTarget</span><br><span class="line">                                   selector:(SEL)aSelector</span><br><span class="line">                                   userInfo:(<span class="type">id</span>)userInfo</span><br><span class="line">                                    repeats:(<span class="type">BOOL</span>)repeats &#123;</span><br><span class="line">    VCHWeakTimer *weakTimer = [[VCHWeakTimer alloc] init];</span><br><span class="line">    weakTimer.target = aTarget;</span><br><span class="line">    weakTimer.selector = aSelector;</span><br><span class="line">    <span class="keyword">return</span> [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:interval</span><br><span class="line">                                            target:weakTimer</span><br><span class="line">                                          selector:<span class="keyword">@selector</span>(fire:)</span><br><span class="line">                                          userInfo:userInfo</span><br><span class="line">                                           repeats:repeats];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="built_in">NSTimer</span> *)scheduledTimerWithTimeInterval:(<span class="built_in">NSTimeInterval</span>)interval</span><br><span class="line">                                      block:(VCHTimerHandler)block</span><br><span class="line">                                   userInfo:(<span class="type">id</span>)userInfo</span><br><span class="line">                                    repeats:(<span class="type">BOOL</span>)repeats &#123;</span><br><span class="line">    <span class="built_in">NSMutableArray</span> *userInfoArray = [<span class="built_in">NSMutableArray</span> arrayWithObject:[block <span class="keyword">copy</span>]];</span><br><span class="line">    <span class="keyword">if</span> (userInfo != <span class="literal">nil</span>) &#123;</span><br><span class="line">        [userInfoArray addObject:userInfo];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> [<span class="keyword">self</span> scheduledTimerWithTimeInterval:interval</span><br><span class="line">                                         target:<span class="keyword">self</span></span><br><span class="line">                                       selector:<span class="keyword">@selector</span>(_timerBlockInvoke:)</span><br><span class="line">                                       userInfo:userInfoArray</span><br><span class="line">                                        repeats:repeats];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)_timerBlockInvoke:(<span class="built_in">NSArray</span> *)userInfo &#123;</span><br><span class="line">    VCHTimerHandler block = userInfo[<span class="number">0</span>];</span><br><span class="line">    <span class="type">id</span> info = <span class="literal">nil</span>;</span><br><span class="line">    <span class="keyword">if</span> (userInfo.count == <span class="number">2</span>) &#123;</span><br><span class="line">        info = userInfo[<span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    block ? block(info) : <span class="literal">nil</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>

<h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>直接使用 NSTimer 可能会发生内存泄漏，一定要想办法处理掉这个问题。</p>
<h1 id="全书-·-完"><a href="#全书-·-完" class="headerlink" title="全书 · 完"></a><center><font color="red">全书 · 完</font></center></h1>
      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E4%B8%83%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(1)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E4%B8%83%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(1)/" class="post-title-link" itemprop="url">《Effective Objective-C 2.0》第七章阅读笔记(1)</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-28 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-28T00:00:00+08:00">2018-05-28</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:21:36" itemprop="dateModified" datetime="2018-07-01T16:21:36+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="第七章：系统框架-1"><a href="#第七章：系统框架-1" class="headerlink" title="第七章：系统框架(1)"></a><center>第七章：系统框架(1)</center></h1><h2 id="第47条：熟悉系统框架"><a href="#第47条：熟悉系统框架" class="headerlink" title="第47条：熟悉系统框架"></a>第47条：熟悉系统框架</h2><h4 id="Foundation"><a href="#Foundation" class="headerlink" title="Foundation"></a>Foundation</h4><p>Foundation 框架中的类，使用 NS 这个前缀，此前缀是在 Objective-C 语言用作 NeXTSTEP 操作系统的编程语言时首度确定的。Foundation 框架是 Objective-C 应用程序的基础。Foundation 框架不仅提供了 collection 等基础核心功能，而且还提供了字符串处理这样的复杂功能。</p>
<h4 id="CoreFoundation"><a href="#CoreFoundation" class="headerlink" title="CoreFoundation"></a>CoreFoundation</h4><p>CoreFoundation 框架不是 Objective-C 框架，但它却是 Objective-C 应用程序时所应熟悉的重要框架，Foundation 框架中的许多功能，都可以在此框架中找到对应的 C 语言 API。CoreFoundation 与 Foundation 名字相似、联系紧密。能做到“无缝桥接”，可以把 CoreFoundation 框架中的 C 语言数据结构平滑转换为 Foundation 中的 Objective-C 对象，也可以反向转换。比如：NSString 与 CFString 可以互转。</p>
<h4 id="CFNetWork"><a href="#CFNetWork" class="headerlink" title="CFNetWork"></a>CFNetWork</h4><p>此框架提供了 C 语言级别的网络通信能力，它将”BSD套接字”（BSD socket）抽象成易于使用的网络接口。而 Foundation 则将该框架里的部分内容封装为 Objective-C 语言的接口，以便于进行网络通信，例如可以用 NSURLConnection 从 URL 中下载数据。</p>
<h4 id="CoreAudio"><a href="#CoreAudio" class="headerlink" title="CoreAudio"></a>CoreAudio</h4><p>该框架所提供的 C 语言 API 可用来操作设备上的音频硬件。这个框架属于比较难用的那种，因为音频处理本身就很复杂。所幸由这套 API 可以抽象出另外一套 Objective-C 式的 API，用后者来处理音频问题会更简单些。</p>
<h4 id="AVFoundation"><a href="#AVFoundation" class="headerlink" title="AVFoundation"></a>AVFoundation</h4><p>此框架所提供的 Objective-C 对象可用来回放并录制音频及视频，比如能够在 UI 视图类里播放视频。</p>
<h4 id="CoreData"><a href="#CoreData" class="headerlink" title="CoreData"></a>CoreData</h4><p>此框架提供的 Objective-C 接口可以将对象放入数据库，便于持久保存。CoreData 会处理数据的获取及存储事宜，而且可以跨越 Mac OS X 及 iOS 平台。</p>
<h4 id="CoreText"><a href="#CoreText" class="headerlink" title="CoreText"></a>CoreText</h4><p>此框架提供的 C 语言接口可以高效执行文字排版及渲染操作。</p>
<h4 id="UIKit"><a href="#UIKit" class="headerlink" title="UIKit"></a>UIKit</h4><p>我们可能会编写使用 UI 框架的 Mac OS X 或 iOS 应用程序。这两个平台的核心 UI 框架分别叫做 Appkit 及 UIKit，它们都提供了构建在Foundation 与 CoreFoundation 之上的 Objective-C 类。框架里含有 UI 元素，也含有粘合机制，令开发者可将所有相关内容组装为应用程序。</p>
<h4 id="CoreAnimation"><a href="#CoreAnimation" class="headerlink" title="CoreAnimation"></a>CoreAnimation</h4><p>CoreAnimation 是用 Objective-C 语言写成的，它提供了一些工具，而 UI 框架则用这些工具来渲染图形并播放动画。开发者编程时可能从来不会深入到这种级别，不过知道该该框架总是好的。CoreAnimation 本身并不是框架，它是 QuartzCore 框架的一部分。然而在框架的国度里，CoreAnimation 仍应算作“一等公民”(first-class citizen)。</p>
<h4 id="CoreGraphics"><a href="#CoreGraphics" class="headerlink" title="CoreGraphics"></a>CoreGraphics</h4><p>CoreGraphics 框架以 C 语言写成，其中提供了 2D 渲染所必备的数据结构与函数。例如，其中定义了 CGPoint、CGSize、CGRect 等数据结构，而 UIKit 框架中 UIView 类在确定视图控件之间的相对位置时，这些数据结构都要用到。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>系统框架给我们提供了构建应用程序所需的核心功能。<br>Objective-C 编程经常需要使用底层的 C 语言级 API。好处是可以绕过 Objective-C 运行期系统，从而提供执行速度。<br>由于 ARC 只负责 Objective-C 对象，所以使用 C 语言级别的 API 时尤其要注意内存管理问题。</p>
<h2 id="第48条：多用块枚举，少用-for-循环"><a href="#第48条：多用块枚举，少用-for-循环" class="headerlink" title="第48条：多用块枚举，少用 for 循环"></a>第48条：多用块枚举，少用 for 循环</h2><p>在编程中经常需要列举 collection 中的元素，当前的 Objective-C 语言有很多种办法实现此功能，比较常用的有，标准 C 语言循环， Objective-C 2.0 的快速遍历，以及“块”循环。</p>
<h3 id="for-循环"><a href="#for-循环" class="headerlink" title="for 循环"></a>for 循环</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Dictionary</span></span><br><span class="line"><span class="built_in">NSArray</span> *anArray = <span class="comment">/*...*/</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; anArray.count; i++) &#123;</span><br><span class="line">	<span class="type">id</span> object = anArray[i];</span><br><span class="line">	<span class="comment">// Do something with &#x27;object&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// NSDictionary</span></span><br><span class="line"><span class="built_in">NSDictionary</span> *aDictionary = <span class="comment">/*...*/</span>;</span><br><span class="line"><span class="built_in">NSArray</span> *keys = [aDictionary allKeys];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; keys.count; i++) &#123;</span><br><span class="line">	<span class="type">id</span> key = keys[i];</span><br><span class="line">	<span class="type">id</span> value = aDictionary[key];</span><br><span class="line">	<span class="comment">// Do something with &#x27;key&#x27; and &#x27;value&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// NSSet</span></span><br><span class="line"><span class="built_in">NSSet</span> *aSet = <span class="comment">/*...*/</span>;</span><br><span class="line"><span class="built_in">NSArray</span> *objects = [aSet allObjects];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; objects.count; i++) &#123;</span><br><span class="line">	<span class="type">id</span> object = objects[i];</span><br><span class="line">	<span class="comment">// Do something with &#x27;object&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>for 循环的缺点就是有时需要创建额外的对象才能完成遍历。  </p>
<p>在这里，字典与 set 都是”无序的”（ unordered ），所以无法根据特定的整数下标来直接访问其中的值。于是，就需要先获取字典里的所有键或是 set 里的所有对象，这两种情况下，都可以在获取到的有序数组上遍历，以便借此访问原字典及原 set 中得值。创建这个附加数组会有额外的开销，而且还会多创建一个数组对象，它会保留 collection 中得所有元素对象。</p>
<h3 id="快速遍历"><a href="#快速遍历" class="headerlink" title="快速遍历"></a>快速遍历</h3><p>Objective-C 2.0 引入了快速遍历这一功能。快速遍历语法更简洁，它为 for 循环开设了 in 关键字。这个关键字大幅简化了遍历 collection 所需的语法。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NSArray</span></span><br><span class="line"><span class="built_in">NSArray</span> *anArray = <span class="comment">/* ... */</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">id</span> object <span class="keyword">in</span> anArray) &#123;</span><br><span class="line">	<span class="comment">// Do something with &#x27;object&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// NSDictionary </span></span><br><span class="line"><span class="built_in">NSDictionary</span> *aDictionary = <span class="comment">/* ... */</span>; </span><br><span class="line"><span class="keyword">for</span> (<span class="type">id</span> key <span class="keyword">in</span> aDictionary) &#123; </span><br><span class="line">	<span class="type">id</span> value = aDictionary[key]; </span><br><span class="line">	<span class="comment">// Do something with &#x27;key&#x27; and &#x27;value&#x27; </span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// NSSet</span></span><br><span class="line"><span class="built_in">NSSet</span> *aSet = <span class="comment">/* ... */</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">id</span> object <span class="keyword">in</span> aSet) &#123;</span><br><span class="line">	<span class="comment">// Do something with &#x27;object&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这种遍历方式简单且效率高，然而如果在遍历字典时需要同时获取键与值，那么会多出来一步。而且，与传统 for 循环不同，这种遍历方式无法轻松获取当前遍历操作所针对的下标。</p>
<h3 id="基于块的遍历方式"><a href="#基于块的遍历方式" class="headerlink" title="基于块的遍历方式"></a>基于块的遍历方式</h3><p>在当前的 Objective-C 语言中，最新引入的一种做法就是基于块来遍历。NSArray、NSDictionary、NSSet 中定义了下面这个方法，可以实现最基本的遍历功能：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NSArray</span></span><br><span class="line">- (<span class="type">void</span>)enumerateObjectsUsingBlock:(<span class="type">void</span>(^)(<span class="type">id</span> object, <span class="built_in">NSUInteger</span> idx, <span class="type">BOOL</span> *stop))block;</span><br><span class="line"><span class="comment">// NSDictionary</span></span><br><span class="line">- (<span class="type">void</span>)enumerateKeysAndObjectsUsingBlock:(<span class="type">void</span>(^)(<span class="type">id</span> key, <span class="type">id</span> object, <span class="type">BOOL</span> *stop))block;</span><br><span class="line"><span class="comment">// NSSet</span></span><br><span class="line">- (<span class="type">void</span>)enumerateObjectsUsingBlock:(<span class="type">void</span>(^)(<span class="type">id</span> object, <span class="type">BOOL</span> *stop))block;</span><br></pre></td></tr></table></figure>
<p>NSArray 对应的块有三个参数，分别是当前迭代所针对的对象、所针对的下标，以及指向布尔值的指针。前两个参数的含义不言而喻。而通过第三个参数所提供的机制，开发者可以终止遍历操作。其他两个类似。<br>使用下面代码可以遍历数组   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NSArray</span></span><br><span class="line"><span class="built_in">NSArray</span> *anArray = <span class="comment">/* ... */</span>;</span><br><span class="line">[anArray enumerateObjectsUsingBlock:^(<span class="type">id</span> object, <span class="built_in">NSUInteger</span> idx, <span class="type">BOOL</span> *stop) &#123;</span><br><span class="line">	<span class="comment">// Do something with &#x27;object&#x27;</span></span><br><span class="line">	<span class="keyword">if</span> (shouldStop) &#123;</span><br><span class="line">		*stop = <span class="literal">YES</span>;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;];</span><br><span class="line"></span><br><span class="line"><span class="comment">// NSDictionary</span></span><br><span class="line"><span class="built_in">NSDictionary</span> *aDictionary = <span class="comment">/* ... */</span>;</span><br><span class="line">[aDictionary enumerateKeysAndObjectsUsingBlock:^(<span class="type">id</span> key, <span class="type">id</span> object, <span class="type">BOOL</span> *stop) &#123;</span><br><span class="line">	<span class="comment">// Do something with &#x27;key&#x27; and &#x27;object&#x27;</span></span><br><span class="line">	<span class="keyword">if</span> (shouldStop) &#123;</span><br><span class="line">		*stop = <span class="literal">YES</span>;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;];</span><br><span class="line"></span><br><span class="line"><span class="comment">// NSSet</span></span><br><span class="line"><span class="built_in">NSSet</span> *aSet = <span class="comment">/* ... */</span>;</span><br><span class="line">[aSet enumerateObjectsUsingBlock:^(<span class="type">id</span> object, <span class="type">BOOL</span> *stop) &#123;</span><br><span class="line">	<span class="comment">// Do something with &#x27;object&#x27;</span></span><br><span class="line">	<span class="keyword">if</span> (shouldStop) &#123;</span><br><span class="line">		*stop = <span class="literal">YES</span>;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;];</span><br></pre></td></tr></table></figure>
<p>遍历时可以直接从 block 里获取更多信息。在遍历数组时，可以知道当前所针对的下标。遍历有序 NSSet（NSOrderedSet）时也一样。而在遍历字典时，无须额外编码，即可同时获取键与值，因而省去了根据给定键来获取对应值这一步。用这种方式遍历字典，可以同时得知键与值，这很可能比其他方式快很多，因为在字典内部的数据结构中，键与值本来就是存储在一起的。同时，使用这种方法能够修改 block 的方法名，以免进行类型转换的操作，从效果上讲，相当于把本来需要执行的类型转换操作交给block方法签名来做。</p>
<p>用此方式也可以执行反向遍历。数组、字典、set都实现了前述方法的另一个版本，使开发者可向其传入“选项掩码”（option mask）：   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)enumerateObjectsWithOptions:(<span class="built_in">NSEnumerationOptions</span>)options usingBlock:(<span class="type">void</span>(^)(<span class="type">id</span> obj, <span class="built_in">NSUInteger</span> idx, <span class="type">BOOL</span> *stop))block; </span><br><span class="line">- (<span class="type">void</span>)enumerateKeysAndObjectsWithOptions:(<span class="built_in">NSEnumerationOptions</span>)options usingBlock: (<span class="type">void</span>(^)(<span class="type">id</span> key, <span class="type">id</span> obj, <span class="type">BOOL</span> *stop))block;</span><br></pre></td></tr></table></figure>
<p>NSEnumerationOptions 类型是个 enum，其各种取值可用“按位或”（bitwise OR）连接，用以表明遍历方式。</p>
<p>总体来看，block 枚举法拥有其他遍历方式都具备的优势，而且还能带来更多好处。与快速遍历法相比，它要多用一些代码，可是却能提供遍历时所针对的下标，在遍历字典时也能同时提供键与值，而且还有选项可以开启并发迭代功能。</p>
<h2 id="第49条：对自定义其内存管理语义的-collection-使用无缝桥接"><a href="#第49条：对自定义其内存管理语义的-collection-使用无缝桥接" class="headerlink" title="第49条：对自定义其内存管理语义的 collection 使用无缝桥接"></a>第49条：对自定义其内存管理语义的 collection 使用无缝桥接</h2><p>使用 “无缝桥接” 技术，可以在定义于 Foundation 框架中的 Objective-C 类和定义于 CoreFoundation 框架中 C 数据结构之间相互转换。   </p>
<p>下面代码演示了简单的无缝桥接：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">NSArray</span> *anNSArray = @[@<span class="number">1</span>,@<span class="number">2</span>,@<span class="number">3</span>,@<span class="number">4</span>,@<span class="number">5</span>];  </span><br><span class="line"><span class="built_in">CFArrayRef</span> aCFArray = (__bridge <span class="built_in">CFArrayRef</span>)anNSArray;  </span><br><span class="line"><span class="built_in">NSLog</span>(<span class="string">@&quot;size of array = %li&quot;</span>,<span class="built_in">CFArrayGetCount</span>(aCFArray));  </span><br><span class="line"><span class="comment">// Output：size of array = 5</span></span><br></pre></td></tr></table></figure>
<p>转换操作中的 __bridge 告诉 ARC 如何处理所涉及的 Objective-C 对象。__bridge 本身的意思是：ARC 仍然具备这个 Objective-C 对象的所有权。而 __bridge_retained 则与之相反，意味着 ARC 将交出对象的所有权。若是前面那段代码改用它来实现，那么用完数组之后就要加上CFRelease(aCFArray)以释放其内存。与之相似，反向转换可通过 __bridge_transfer 来实现。那么，为什么需要桥接呢？那是因为Foundation 框架中 Objective-C 类所具备的某些功能，是 CoreFoundation 框架中 C 数据结构所不具备的，反之亦然。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E5%85%AD%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(3)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E5%85%AD%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(3)/" class="post-title-link" itemprop="url">《Effective Objective-C 2.0》第六章阅读笔记(3)</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-24 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-24T00:00:00+08:00">2018-05-24</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:21:26" itemprop="dateModified" datetime="2018-07-01T16:21:26+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="第六章：块与大中枢派发-3"><a href="#第六章：块与大中枢派发-3" class="headerlink" title="第六章：块与大中枢派发(3)"></a><center>第六章：块与大中枢派发(3)</center></h1><h2 id="第44条：通过-Dispatch-Group，根据系统资源状况来执行任务"><a href="#第44条：通过-Dispatch-Group，根据系统资源状况来执行任务" class="headerlink" title="第44条：通过 Dispatch Group，根据系统资源状况来执行任务"></a>第44条：通过 Dispatch Group，根据系统资源状况来执行任务</h2><p>dispatch group 是 GCD 的一项特性，能够把任务分组。调用者可以等待这组任务执行完毕，也可以在提供回调函数之后继续往下执行，这组任务完成时，调用者会得到通知。通过这个功能可以把将要并发执行的多个任务合为一组，于是调用者就可以知道这些任务何时才能全部执行完毕。</p>
<h4 id="创建-dispatch-group"><a href="#创建-dispatch-group" class="headerlink" title="创建 dispatch group"></a>创建 dispatch group</h4><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dispatch_group_t group = dispatch_group_create();</span><br></pre></td></tr></table></figure>

<p>想把任务分组，有两种办法。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> dispatch_group_async(dispatch_group_t group, <span class="built_in">dispatch_queue_t</span> queue, dispatch_block_t block);</span><br></pre></td></tr></table></figure>

<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dispatch_group_enter(dispatch_group_t group);</span><br><span class="line"><span class="comment">// task</span></span><br><span class="line">dispatch_group_leave(dispatch_group_t group);</span><br></pre></td></tr></table></figure>
<p>判断任务完成也有两种方法<br>第一种方法是同步的，等到所有任务完成，才能继续往下执行。  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);</span><br></pre></td></tr></table></figure>
<p>第二种方法是异步的，当所有的任务执行完成，就会触发这个通知。  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> dispatch_group_notify(dispatch_group_t group, <span class="built_in">dispatch_queue_t</span> queue, dispatch_block_t block);</span><br></pre></td></tr></table></figure>

<p>如果想令数组中的每个对象都执行某项任务，并且想等待所有任务执行完毕，那么就可以使用这个GCD特性来实现。同时还可以给任务加上优先级。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, <span class="number">0</span>); </span><br><span class="line"><span class="built_in">dispatch_queue_t</span> highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, <span class="number">0</span>); </span><br><span class="line">dispatch_group_t dispatchGroup = dispatch_group_create();</span><br><span class="line"></span><br><span class="line"><span class="built_in">NSArray</span> *lowPriorityObject;</span><br><span class="line"><span class="built_in">NSArray</span> *highPriorityObject;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="type">id</span> object <span class="keyword">in</span> lowPriorityObject) &#123; </span><br><span class="line">	dispatch_group_async(dispatchGroup, lowPriorityQueue, ^&#123; </span><br><span class="line">		[object task];</span><br><span class="line">	&#125;); </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="type">id</span> object <span class="keyword">in</span> highPriorityObject) &#123; </span><br><span class="line">	dispatch_group_async(dispatchGroup, highPriorityQueue, ^&#123; </span><br><span class="line">		[object task]; </span><br><span class="line">	&#125;); </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^&#123; </span><br><span class="line">	</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>除了像上面这样把任务提交到并发队列之外，也可以把任务提交至各个串行队列中，并用 dispatch group 跟踪其执行状况。如果所有任务都排在同一个串行队列里面，那么 dispatch group 就用处不大了。因为此时，任务总要逐个执行，所以只需在提交完全部任务之后再提交一个块即可，这样做与通过 notify 函数等待 dispatch group 执行完毕后再回调块是等效的。</p>
</blockquote>
<h4 id="dispatch-apply"><a href="#dispatch-apply" class="headerlink" title="dispatch_apply"></a>dispatch_apply</h4><p>dispatch_apply 也是并发，并且是阻塞的，所以有时候我们完全可以使用 dispatch_apply 来代替 dispatch group 来执行任务。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> queue = dispatch_queue_create(<span class="string">&quot;com.vhuichen.queue&quot;</span>, <span class="literal">NULL</span>); </span><br><span class="line">dispatch_apply(count, queue, ^(size_t i) &#123; </span><br><span class="line">	<span class="comment">//Perform task </span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>当有一组任务需要执行时，可以将这一组任务加到 dispatch group 中，当所有任务执行完成后会收到一个通知。</p>
<h2 id="第45条：使用-dispath-once-来执行只需运行一次的线程安全代码"><a href="#第45条：使用-dispath-once-来执行只需运行一次的线程安全代码" class="headerlink" title="第45条：使用 dispath_once 来执行只需运行一次的线程安全代码"></a>第45条：使用 dispath_once 来执行只需运行一次的线程安全代码</h2><p>单例模式（singleton）是我们常用的一种开发模式，常见的一种写法如下：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">+ (<span class="keyword">instancetype</span>)sharedInstance &#123; </span><br><span class="line">	<span class="keyword">static</span> <span class="type">id</span> sharedInstance = <span class="literal">nil</span>; </span><br><span class="line">	<span class="keyword">@synchronized</span> (<span class="keyword">self</span>) &#123; </span><br><span class="line">		<span class="keyword">if</span> (!sharedInstance) &#123; </span><br><span class="line">			sharedInstance = [[<span class="keyword">self</span> alloc] init]; </span><br><span class="line">		&#125; </span><br><span class="line">	&#125; </span><br><span class="line">	<span class="keyword">return</span> sharedInstance; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>也可以通过 GCD 的 dispath_once 来实现，dispath_once 是线程安全的。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">+ (<span class="keyword">instancetype</span>)sharedInstance &#123; </span><br><span class="line">	<span class="keyword">static</span> <span class="type">id</span> sharedInstance = <span class="literal">nil</span>; </span><br><span class="line">	<span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken; </span><br><span class="line">	<span class="built_in">dispatch_once</span>(&amp;onceToken, ^&#123; </span><br><span class="line">		sharedInstance = [[<span class="keyword">self</span> alloc] init]; </span><br><span class="line">	&#125;);</span><br><span class="line">	<span class="keyword">return</span> sharedInstance; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>使用 dispath_once 方式比 @synchronized 方式要快很多</p>
</blockquote>
<h2 id="第46条：不要使用-dispatch-get-current-queue"><a href="#第46条：不要使用-dispatch-get-current-queue" class="headerlink" title="第46条：不要使用 dispatch_get_current_queue"></a>第46条：不要使用 dispatch_get_current_queue</h2><p>使用 GCD 时，经常需要判断当前代码正在哪个队列上执行，文档提供了这个函数：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> dispatch_get_current_queue();</span><br></pre></td></tr></table></figure>
<p>iOS6.0 开始已经正式弃用此函数了。这个函数有个典型的错误用法，就是用它来检测当前队列是不是某个特定的队列，试图以此来避免执行同步派发时可能遇到的死锁问题。<br>下面两个存取方法，用串行队列保证实例变量的访问是线程安全的。  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">NSString</span> *)someString &#123;</span><br><span class="line">	__block <span class="built_in">NSString</span> *localSomeString;</span><br><span class="line">	<span class="built_in">dispatch_sync</span>(_syncQueue, ^&#123;</span><br><span class="line">		localSomeString = _someString;</span><br><span class="line">	&#125;);</span><br><span class="line">	<span class="keyword">return</span> localSomeString;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)setSomeString:(<span class="built_in">NSString</span> *)someString &#123;  </span><br><span class="line">	<span class="built_in">dispatch_async</span>(_syncQueue, ^&#123;</span><br><span class="line">		_someString = someString;</span><br><span class="line">	&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这种写法的问题在于，getter 方法可能会死锁（当 getter 方法恰好就是 _syncQueue 时）。<br>可以将上面的代码稍作修改，只需先判断当前队列是否为 _syncQueue 队列，如果是就不派发，直接执行。这样做就可以另其变得“可重入”</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">NSString</span> *)someString &#123;</span><br><span class="line">	__block <span class="built_in">NSString</span> *localSomeString;</span><br><span class="line">	dispatch_block_t accessorBlock = ^&#123;</span><br><span class="line">		localSomeString = _someString;</span><br><span class="line">	&#125;;</span><br><span class="line">   </span><br><span class="line">	<span class="keyword">if</span> (dispatch_get_current_queue() == _syncQueue) &#123;</span><br><span class="line">		accessorBlock();</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		<span class="built_in">dispatch_sync</span>(_syncQueue, accessorBlock);  </span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> localSomeString;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这样做好像是可以解决问题，但有些情况下还是会出现死锁问题，例如下面的例子：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> queueA = dispatch_queue_create(<span class="string">&quot;com.vhuichen.queueA&quot;</span>, <span class="literal">NULL</span>);  </span><br><span class="line"><span class="built_in">dispatch_queue_t</span> queueB = dispatch_queue_create(<span class="string">&quot;com.vhuichen.queueB&quot;</span>, <span class="literal">NULL</span>);  </span><br><span class="line"></span><br><span class="line"><span class="built_in">dispatch_sync</span>(queueA, ^&#123;</span><br><span class="line">	<span class="built_in">dispatch_sync</span>(queueB, ^&#123;</span><br><span class="line">		dispatch_block_t block = ^&#123; <span class="comment">/* ... */</span> &#125;;</span><br><span class="line">		<span class="keyword">if</span> (dispatch_get_current_queue() == queueA) &#123;</span><br><span class="line">			block();</span><br><span class="line">		&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="built_in">dispatch_sync</span>(queueA, block);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>上面的代码依然会出现死锁。也就是说想通过 dispatch_get_current_queue 来避免死锁问题是不可能的。  </p>
<p>有的 API 可令开发者指定运行回调时所用的队列，但实际上却会把回调块安排在内部的串行同步队列上，而内部队列的目标队列又是开发者所提供的那个队列，那么就会出现死锁。使用 API 的开发者认为在回调块里调用 dispatch_get_current_queue 返回的“当前队列”，总是调用 API 时指定的那个，但实际返回的却是 API 内部的那个队列。  </p>
<p>要解决这个问题，最好的办法是通过 GCD 所提供的功能来设定“队列特有数据”（ queue_specific data ），此功能可以把任意数据以键值对的形式关联到队列里。假如根据指定的键值对获取不到关联数据，那么系统会沿着层级体系一直向上找，直到找到数据或者到达根队列为止。看看下面的例子：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_queue_t</span> queueA = dispatch_queue_create(<span class="string">&quot;com.vhuichen.queueA&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="built_in">dispatch_queue_t</span> queueB = dispatch_queue_create(<span class="string">&quot;com.vhuichen.queueB&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="type">int</span> kQueueSpecific;</span><br><span class="line"><span class="built_in">CFStringRef</span> queueSpecificValue = <span class="built_in">CFSTR</span>(<span class="string">&quot;queueA&quot;</span>);</span><br><span class="line">dispatch_queue_set_specific(queueA, &amp;kQueueSpecific, (<span class="type">void</span> *)queueSpecificValue,  (dispatch_function_t)<span class="built_in">CFRelease</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">dispatch_sync</span>(queueB, ^&#123;</span><br><span class="line">	dispatch_block_t block = ^&#123; <span class="built_in">NSLog</span>(<span class="string">@&quot;no deadlock&quot;</span>); &#125;;</span><br><span class="line">	<span class="built_in">CFStringRef</span> retrievedValue = dispatch_get_specific(&amp;kQueueSpecific);</span><br><span class="line">	<span class="keyword">if</span> (retrievedValue) &#123;</span><br><span class="line">		block();</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		<span class="built_in">dispatch_sync</span>(queueA, block);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>使用 “队列特有数据”（ queue_specific data ）则可以避免由不可重入引发的死锁。  </p>
<h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>dispatch_get_current_queue 函数无法解决由不可重入引发的死锁问题，但“队列特有数据”（ queue_specific data ）可以解决此问题。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E5%85%AD%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(2)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E5%85%AD%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(2)/" class="post-title-link" itemprop="url">《Effective Objective-C 2.0》第六章阅读笔记(2)</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-23 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-23T00:00:00+08:00">2018-05-23</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:20:40" itemprop="dateModified" datetime="2018-07-01T16:20:40+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="第六章：块与大中枢派发-2"><a href="#第六章：块与大中枢派发-2" class="headerlink" title="第六章：块与大中枢派发(2)"></a><center>第六章：块与大中枢派发(2)</center></h1><h2 id="第41条：多用派发队列，少用同步锁"><a href="#第41条：多用派发队列，少用同步锁" class="headerlink" title="第41条：多用派发队列，少用同步锁"></a>第41条：多用派发队列，少用同步锁</h2><p>如果有多个线程要执行同一份代码，那么有时可能会出问题。这种情况下，通常要使用锁来实现同步机制。在GCD出现之前，一般有两种方式可以实现同步  </p>
<h3 id="原始方法：synchronized-amp-NSLock"><a href="#原始方法：synchronized-amp-NSLock" class="headerlink" title="原始方法：synchronized &amp; NSLock"></a>原始方法：synchronized &amp; NSLock</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)synchronizedMethod &#123;</span><br><span class="line">    <span class="keyword">@synchronized</span> (<span class="keyword">self</span>) &#123;</span><br><span class="line">        <span class="comment">// Safe</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">_lock = [[<span class="built_in">NSLock</span> alloc] init];</span><br><span class="line">- (<span class="type">void</span>)synchronizedMethod &#123;</span><br><span class="line">    [_lock lock];</span><br><span class="line">    <span class="comment">// Safe</span></span><br><span class="line">    [_lock unlock];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>滥用 @synchronized(self) 会很危险，因为所有同步块都会彼此抢夺同一个锁。要是有很多个属性都这么写的话，那么每个属性的同步块都要等其他所有同步块执行完毕才能执行。两种方法的使用效率都不高，并且处理不当会造成死锁。</p>
<h3 id="改进方法：串行同步队列"><a href="#改进方法：串行同步队列" class="headerlink" title="改进方法：串行同步队列"></a>改进方法：串行同步队列</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">_syncQueue = dispatch_queue_create(<span class="string">&quot;com.vhuichen.syncQueue&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSString</span> *)someString &#123; </span><br><span class="line">	__block <span class="built_in">NSString</span> *localSomeString; </span><br><span class="line">	<span class="built_in">dispatch_sync</span>(_syncQueue, ^&#123; </span><br><span class="line">		localSomeString = _someString; </span><br><span class="line">	&#125;);</span><br><span class="line">	<span class="keyword">return</span> localSomeString;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)setSomeString:(<span class="built_in">NSString</span> *)someString &#123; </span><br><span class="line">	<span class="built_in">dispatch_sync</span>(_syncQueue, ^&#123; </span><br><span class="line">		_someString = someString; </span><br><span class="line">	&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里有一种方案就是可以把 setter 方法改成异步执行，提升程序的执行速度。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)setSomeString:(<span class="built_in">NSString</span> *)someString &#123; </span><br><span class="line">	<span class="built_in">dispatch_async</span>(_syncQueue, ^&#123; </span><br><span class="line">		_someString = someString; </span><br><span class="line">	&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里需要考虑的是：执行异步派发时，需要拷贝块。若拷贝块所需的时间明显超过执行块所花的时间，那么这种做法将比原来的更慢。只有当拷贝块所花的时间远低于执行块所花的时间时，可以考虑这种异步方法。</p>
<h3 id="最优方案：dispatch-barrier"><a href="#最优方案：dispatch-barrier" class="headerlink" title="最优方案：dispatch_barrier"></a>最优方案：dispatch_barrier</h3><p>事实上，获取值时可以多个同时进行，设置值和获取值不能同时进行。利用这个特点，我们可以对代码再次优化。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">_syncQueue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="literal">NULL</span>); </span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSString</span> *)someString &#123;</span><br><span class="line">	__block <span class="built_in">NSString</span> *localSomeString;</span><br><span class="line">	<span class="built_in">dispatch_sync</span>(_syncQueue, ^&#123;</span><br><span class="line">		localSomeString = _someString;</span><br><span class="line">	&#125;);</span><br><span class="line">	<span class="keyword">return</span> localSomeString;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)setSomeString:(<span class="built_in">NSString</span> *)someString &#123;</span><br><span class="line">	<span class="comment">// 这是使用 async 还是 sync 取决于 block 的业务逻辑复杂度，上面有解释</span></span><br><span class="line">	dispatch_barrier_async(_syncQueue, ^&#123;</span><br><span class="line">		_someString = someString;</span><br><span class="line">	&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面的代码，我们创建的是一个并行队列。读取操作可以并行，但写入操作是单独执行的，因为给它加了栅栏，代码的执行逻辑如下图<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-24/62516339.jpg">  </p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>使用GCD实现同步方式，比使用 synchronized 或 NSLock 更高效。</p>
<h2 id="第42条：多用-GCD，少用-performSelector-系列方法"><a href="#第42条：多用-GCD，少用-performSelector-系列方法" class="headerlink" title="第42条：多用 GCD，少用 performSelector 系列方法"></a>第42条：多用 GCD，少用 performSelector 系列方法</h2><p>performSelector 有几个缺点。</p>
<h4 id="可能会引起内存泄漏"><a href="#可能会引起内存泄漏" class="headerlink" title="可能会引起内存泄漏"></a>可能会引起内存泄漏</h4><p>看下面一段代码</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">SEL selector;</span><br><span class="line"><span class="keyword">if</span> (<span class="comment">/* ... */</span>) &#123;</span><br><span class="line">	selector = <span class="keyword">@selector</span>(newObject);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="comment">/* ... */</span>) &#123;</span><br><span class="line">	selector = <span class="keyword">@selector</span>(<span class="keyword">copy</span>); </span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">	selector = <span class="keyword">@selector</span>(someProperty);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">id</span> ret = [object performSelector:selector];</span><br></pre></td></tr></table></figure>
<p>编译器会发出如下警示信息  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">warning:PerformSelector may cause a leak because its selector is unknown</span><br></pre></td></tr></table></figure>
<p>原因在于，编译器并不知道将要调用的选择子的方法签名及返回值。由于编译器不知道方法名，所以就没办法运用 ARC 的内存管理规则来判定返回值是不是应该释放。鉴于此，ARC采用了比较谨慎的做法，就是不添加释放操作。然而这么做可能导致内存泄漏，因为方法在返回对象时可能已经将其保留了。</p>
<h4 id="返回值只能是-void-或对象类型"><a href="#返回值只能是-void-或对象类型" class="headerlink" title="返回值只能是 void 或对象类型"></a>返回值只能是 void 或对象类型</h4><p>如果想返回整数或浮点数等类型的值，那么就需要执行一些复杂的转换操作。如果返回的是结构体，则不能使用 performSelector 。</p>
<h4 id="传入参数有限制"><a href="#传入参数有限制" class="headerlink" title="传入参数有限制"></a>传入参数有限制</h4><p>传入参数必须为对象类型，最多只有两个限制。</p>
<h3 id="改进（GCD）"><a href="#改进（GCD）" class="headerlink" title="改进（GCD）"></a>改进（GCD）</h3><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[<span class="keyword">self</span> performSelectorOnMainThread:<span class="keyword">@selector</span>(aSelector) withObject:<span class="literal">nil</span> waitUntilDone:<span class="literal">NO</span>];</span><br></pre></td></tr></table></figure>
<p>上面的功能可以通过 GCD 来实现 </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dispatch_async</span>(dispatch_get_main_queue(), ^&#123;</span><br><span class="line">	[<span class="keyword">self</span> aSelector];</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>其它 performSelector 的方法也一样可以用 GCD 的方法代替。</p>
<h2 id="第43条：掌握-GCD-及-NSOperationQueue-的使用时机"><a href="#第43条：掌握-GCD-及-NSOperationQueue-的使用时机" class="headerlink" title="第43条：掌握 GCD 及 NSOperationQueue 的使用时机"></a>第43条：掌握 GCD 及 NSOperationQueue 的使用时机</h2><h3 id="使用-NSOperationQueue-优点"><a href="#使用-NSOperationQueue-优点" class="headerlink" title="使用 NSOperationQueue 优点"></a>使用 NSOperationQueue 优点</h3><h4 id="取消某个操作"><a href="#取消某个操作" class="headerlink" title="取消某个操作"></a>取消某个操作</h4><p>使用 NSOperationQueue ，想要取消操作队列是很容易的。运行任务之前，可以在 NSOperation 对象上调用 cancel 方法，该方法会设置对象内的标志位，用以表明此任务不需执行，不过，已经启动的任务无法取消。GCD 则无法直接取消。</p>
<h4 id="指定操作间的依赖关系"><a href="#指定操作间的依赖关系" class="headerlink" title="指定操作间的依赖关系"></a>指定操作间的依赖关系</h4><p>一个操作可以依赖其他多个操作。开发者能够制定操作之间的依赖体系，使特定的操作必须在另外一个操作顺利执行完毕后方可执行。</p>
<h4 id="通过键值观测机制监控-NSOperation-对象的属性"><a href="#通过键值观测机制监控-NSOperation-对象的属性" class="headerlink" title="通过键值观测机制监控 NSOperation 对象的属性"></a>通过键值观测机制监控 NSOperation 对象的属性</h4><p>NSOperation 对象有许多属性都适合通过键值观测机制（KVO）来监听。比如可以通过 isCancelled 属性来判断任务是否已取消，又比如可以通过 isFinished 属性来判断任务是否已完成。</p>
<h4 id="指定操作的优先级"><a href="#指定操作的优先级" class="headerlink" title="指定操作的优先级"></a>指定操作的优先级</h4><p>操作的优先级表示此操作与队列中其他操作之间的优先级关系。优先级高的操作先执行，优先级低的后执行。</p>
<h4 id="重用-NSOperation-对象"><a href="#重用-NSOperation-对象" class="headerlink" title="重用 NSOperation 对象"></a>重用 NSOperation 对象</h4><p>系统内置了一些 NSOperation 的子类（比如 NSBlockOperation）以供开发者调用，要是不想用这些子类，可以自己创建。这些类就是普通的 Objective-C 对象，能够存放任何信息。对象在执行时可以充分利用存于其中的信息，而且还可以随意调用定义在类中的方法。NSOperation 类符合软件开发中的“不重复”（Don’t Repeat Yourself，DRY）原则。</p>
<h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>GCD 操作简单，NSOperation 则功能更多。熟练掌握两种方式，在各种各样的场景中运用自如。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/iOS%E5%BC%80%E5%8F%91%E4%B9%8B%20URL%20Schemes/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/iOS%E5%BC%80%E5%8F%91%E4%B9%8B%20URL%20Schemes/" class="post-title-link" itemprop="url">iOS开发之 URL Schemes</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-15 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-15T00:00:00+08:00">2018-05-15</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:20:28" itemprop="dateModified" datetime="2018-07-01T16:20:28+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h3 id="什么是-URL-Schemes"><a href="#什么是-URL-Schemes" class="headerlink" title="什么是 URL Schemes"></a>什么是 URL Schemes</h3><p>URL（Uniform Resoure Locator：统一资源定位器）一般就是我们所说的网址，通过 URL 可以访问我们想要访问的服务和资源。</p>
<p>URL的地址格式排列为：<strong>scheme:&#x2F;&#x2F;host:port&#x2F;path</strong>,比如 URL 地址： <strong><a href="https://vhuichen.github.io/tags/">https://vhuichen.github.io/tags/</a></strong> ，这里的 https 就是 Schemes 。又比如支付宝的 URL Schemes 是<strong>alipay:&#x2F;&#x2F;</strong> ，我们在 Safari 的地址栏输入就可以打开支付宝了。</p>
<p>在iOS系统中，我们可以通过 URL Schemes 开定位APP的位置，通过 URL Schemes 实现APP跳转。一般情况下每个 APP 的 URL Schemes 都是不一样的，如果出现相同的 URL Schemes ，那么最后安装的 APP 的 URL Schemes 有效。</p>
<h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>创建两个Project，ProjectA 和 ProjectB ，设置两个项目的 URL Schemes 和项目名相同（这里只做测试用）。具体怎么设置如下图（白名单后面会讲到），这里只放了一张截图。    </p>
<p><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-15/37536164.jpg"></p>
<p>两个 Project 的实现代码实现如下：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ProjectA</span></span><br><span class="line">- (<span class="keyword">IBAction</span>)projectButtonClick:(<span class="type">id</span>)sender &#123;</span><br><span class="line">    <span class="built_in">NSURL</span> *url = [<span class="built_in">NSURL</span> URLWithString:<span class="string">@&quot;ProjectB://&quot;</span>];</span><br><span class="line">    <span class="keyword">if</span> ([[<span class="built_in">UIApplication</span> sharedApplication] canOpenURL:url]) &#123;</span><br><span class="line">        [[<span class="built_in">UIApplication</span> sharedApplication] openURL:url];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ProjectB</span></span><br><span class="line">- (<span class="keyword">IBAction</span>)buttonClick:(<span class="type">id</span>)sender &#123;</span><br><span class="line">    <span class="built_in">NSURL</span> *url = [<span class="built_in">NSURL</span> URLWithString:<span class="string">@&quot;ProjectA://&quot;</span>];</span><br><span class="line">    <span class="keyword">if</span> ([[<span class="built_in">UIApplication</span> sharedApplication] canOpenURL:url]) &#123;</span><br><span class="line">        [[<span class="built_in">UIApplication</span> sharedApplication] openURL:url];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>实现点击 ProjectA 的按钮跳转到 ProjectB ，点击 ProjectB 的按钮跳转到 ProjectA。就这样简单几句代码实现了应用之间的跳转。</p>
<h4 id="iOS10-新接口"><a href="#iOS10-新接口" class="headerlink" title="iOS10 新接口"></a>iOS10 新接口</h4><p>在iOS10中，废弃了 <strong>openURL</strong> 这个接口改用下面这个接口  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)openURL:(<span class="built_in">NSURL</span>*)url options:(<span class="built_in">NSDictionary</span>&lt;<span class="built_in">NSString</span> *, <span class="type">id</span>&gt; *)options completionHandler:(<span class="type">void</span> (^ __<span class="keyword">nullable</span>)(<span class="type">BOOL</span> success))completion <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">10</span>_0) <span class="built_in">NS_EXTENSION_UNAVAILABLE_IOS</span>(<span class="string">&quot;&quot;</span>);</span><br></pre></td></tr></table></figure>
<p>字典参数 options 目前有一个关键字 <strong>UIApplicationOpenURLOptionUniversalLinksOnly</strong> 对应的值为 BOOL 类型。默认情况下 值为 NO。如果设置成 YES，那么只能通过 URL 对应的APP才能打开这个链接，如果没有安装对应的APP，那么 completionHandler 中就会返回 NO。默认情况下是通过 Safari 来打开这个 URL 的。</p>
<h3 id="白名单"><a href="#白名单" class="headerlink" title="白名单"></a>白名单</h3><p>iOS9 开始新增了白名单，当没有添加白名单时 调用 <strong>canOpenURL:</strong> 会返回 NO 。并打印出log信息  </p>
<blockquote>
<p>-canOpenURL: failed for URL: “ProjectB:&#x2F;&#x2F;“ - error: “This app is not allowed to query for scheme projectb”</p>
</blockquote>
<p>添加白名单只需要在 Info.plist 添加 <strong>LSApplicationQueriesSchemes</strong> 关键字，选择数组类型并设置 URL Schemes 的值。具体可以看上图。</p>
<h3 id="传参"><a href="#传参" class="headerlink" title="传参"></a>传参</h3><p>和我们平时用的 URL 一样，也可以在 URL Schemes 后面加参数，例如：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ProjectA</span></span><br><span class="line">- (<span class="keyword">IBAction</span>)projectButtonClick:(<span class="type">id</span>)sender &#123;</span><br><span class="line">    <span class="built_in">NSURL</span> *url = [<span class="built_in">NSURL</span> URLWithString:<span class="string">@&quot;ProjectB://title=mytitle&amp;content=mycontent&quot;</span>];</span><br><span class="line">    <span class="keyword">if</span> ([[<span class="built_in">UIApplication</span> sharedApplication] canOpenURL:url]) &#123;</span><br><span class="line">        [[<span class="built_in">UIApplication</span> sharedApplication] openURL:url];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意：参数之间不能有空格</p>
</blockquote>
<p>URL Schemes 对应的APP可以在回调函数中处理传过来的参数。</p>
<h3 id="回调函数"><a href="#回调函数" class="headerlink" title="回调函数"></a>回调函数</h3><p>当应用是通过 URL Schemes 启动时，会调用相应的回调函数，系统提供了三个回调函数。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// iOS9及以上</span></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)app openURL:(<span class="built_in">NSURL</span> *)url options:(<span class="built_in">NSDictionary</span>&lt;<span class="built_in">UIApplicationOpenURLOptionsKey</span>,<span class="type">id</span>&gt; *)options &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;B0:%@&quot;</span>,url);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// iOS8</span></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application openURL:(<span class="built_in">NSURL</span> *)url sourceApplication:(<span class="built_in">NSString</span> *)sourceApplication annotation:(<span class="type">id</span>)annotation &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;B1:%@&quot;</span>,url);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// iOS8</span></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application handleOpenURL:(<span class="built_in">NSURL</span> *)url &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;B2:%@&quot;</span>,url);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>iOS9及以上会优先调用第一个方法，iOS9以下会优先调用第二个方法。<br>这里我们接收到的 url 中就包含了参数信息。</p>
<h4 id="UIApplicationOpenURLOptionsKey"><a href="#UIApplicationOpenURLOptionsKey" class="headerlink" title="UIApplicationOpenURLOptionsKey"></a>UIApplicationOpenURLOptionsKey</h4><p>最新的回调接口提供了字典参数，这个字典有三个 key  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UIKIT_EXTERN</span> <span class="built_in">UIApplicationOpenURLOptionsKey</span> <span class="keyword">const</span> <span class="built_in">UIApplicationOpenURLOptionsSourceApplicationKey</span> <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">9</span>_0);   <span class="comment">// value is an NSString containing the bundle ID of the originating application</span></span><br><span class="line"><span class="built_in">UIKIT_EXTERN</span> <span class="built_in">UIApplicationOpenURLOptionsKey</span> <span class="keyword">const</span> <span class="built_in">UIApplicationOpenURLOptionsAnnotationKey</span> <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">9</span>_0);   <span class="comment">// value is a property-list typed object corresponding to what the originating application passed in UIDocumentInteractionController&#x27;s annotation property</span></span><br><span class="line"><span class="built_in">UIKIT_EXTERN</span> <span class="built_in">UIApplicationOpenURLOptionsKey</span> <span class="keyword">const</span> <span class="built_in">UIApplicationOpenURLOptionsOpenInPlaceKey</span> <span class="built_in">NS_AVAILABLE_IOS</span>(<span class="number">9</span>_0);   <span class="comment">// value is a bool NSNumber, set to YES if the file needs to be copied before use</span></span><br></pre></td></tr></table></figure>
<p>默认情况下，会传入两个参数</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="built_in">UIApplicationOpenURLOptionsOpenInPlaceKey</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">UIApplicationOpenURLOptionsSourceApplicationKey</span> = <span class="string">&quot;com.xxxx.ProjectB&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>UIApplicationOpenURLOptionsSourceApplicationKey</strong> 传入的是原应用的Bundle ID<br>我们可以通过判断这个 Bundle ID 来决定执行哪项操作以及返回值。例如我们可以这样写  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)app openURL:(<span class="built_in">NSURL</span> *)url options:(<span class="built_in">NSDictionary</span>&lt;<span class="built_in">UIApplicationOpenURLOptionsKey</span>,<span class="type">id</span>&gt; *)options &#123;</span><br><span class="line">    <span class="keyword">if</span>([options[<span class="built_in">UIApplicationOpenURLOptionsSourceApplicationKey</span>] isEqualToString:<span class="string">@&quot;com.xxxx.ProjectB&quot;</span>]) &#123;</span><br><span class="line">        <span class="comment">// do something</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NO</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>疑问：这里我测试发现，不管返回值是否为真，结果都一样。</p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/iOS%E5%9F%BA%E7%A1%80%E4%B9%8B%E4%B8%80%E4%B8%AAWorkspace%E4%B8%8B%E6%9C%89%E5%A4%9A%E4%B8%AAProject/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/iOS%E5%9F%BA%E7%A1%80%E4%B9%8B%E4%B8%80%E4%B8%AAWorkspace%E4%B8%8B%E6%9C%89%E5%A4%9A%E4%B8%AAProject/" class="post-title-link" itemprop="url">iOS基础之一个Workspace下有多个Project</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-15 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-15T00:00:00+08:00">2018-05-15</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:20:16" itemprop="dateModified" datetime="2018-07-01T16:20:16+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%9F%BA%E7%A1%80/" itemprop="url" rel="index"><span itemprop="name">iOS基础</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h3 id="新建一个-Workspace"><a href="#新建一个-Workspace" class="headerlink" title="新建一个 Workspace"></a>新建一个 Workspace</h3><p>先创建一个名为 MultiProject 的 .xcworkspace 文件 ，放到 MultiProject 这个文件夹下。<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-15/12606269.jpg?imageView2/2/h/400"></p>
<h3 id="将-Project-添加到-Workspace-中"><a href="#将-Project-添加到-Workspace-中" class="headerlink" title="将 Project 添加到 Workspace 中"></a>将 Project 添加到 Workspace 中</h3><h4 id="方法一：添加已创建好的-Project"><a href="#方法一：添加已创建好的-Project" class="headerlink" title="方法一：添加已创建好的 Project"></a>方法一：添加已创建好的 Project</h4><p>先创建一个名为 ProjectA 的 Project。<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-15/42111891.jpg?imageView2/2/h/400"><br>创建好 Project 后。打开 MultiProject.xcworkspace 文件。  </p>
<p>点击 File -&gt; Add Files to “Workspace Name”，找到刚创建项目的 ProjectA.xcodeproj 文件，添加。此时 ProjectA 已经加到 MultiProject 这个工作空间下了。</p>
<h4 id="方法二：创建-Project-时就添加到-Workspace-中"><a href="#方法二：创建-Project-时就添加到-Workspace-中" class="headerlink" title="方法二：创建 Project 时就添加到 Workspace 中"></a>方法二：创建 Project 时就添加到 Workspace 中</h4><p>在创建 Project 整个过程的最后一步，会是这样的界面<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-15/5982352.jpg?imageView2/2/h/500"><br>在红色框中选择对应的 Workspace，点击 Create 后，刚创建的 Project 就添加到 Workspace 中了。</p>
<blockquote>
<p>注意：一般会将创建好的 Project 放到 Workspace 目录下。</p>
</blockquote>
<h3 id="CocoaPods-安装"><a href="#CocoaPods-安装" class="headerlink" title="CocoaPods 安装"></a>CocoaPods 安装</h3><p>在 MultiProject.xcworkspace 文件的目录下创建 Podfile 文件。内容如下  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">platform :ios, <span class="string">&#x27;8.0&#x27;</span></span><br><span class="line">workspace <span class="string">&#x27;MultiProject.xcworkspace&#x27;</span></span><br><span class="line"></span><br><span class="line">target <span class="string">&#x27;ProjectA&#x27;</span> <span class="keyword">do</span></span><br><span class="line">project <span class="string">&#x27;ProjectA/ProjectA.xcodeproj&#x27;</span></span><br><span class="line"></span><br><span class="line">pod <span class="string">&#x27;SDWebImage&#x27;</span></span><br><span class="line">pod <span class="string">&#x27;iVersion&#x27;</span></span><br><span class="line"></span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">target <span class="string">&#x27;ProjectB&#x27;</span> <span class="keyword">do</span></span><br><span class="line">project <span class="string">&#x27;ProjectB/ProjectB.xcodeproj&#x27;</span></span><br><span class="line"></span><br><span class="line">pod <span class="string">&#x27;SDWebImage&#x27;</span></span><br><span class="line">pod <span class="string">&#x27;AFNetworking&#x27;</span></span><br><span class="line">pod <span class="string">&#x27;iVersion&#x27;</span></span><br><span class="line"></span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<p>最后在命令行中进入该目录，执行 <strong>pod install</strong> 命令，OK 搞定。  </p>
<p>最终的目录文件如下：<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-15/86708408.jpg"></p>
<p>项目内结构如下：<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-15/23970018.jpg?imageView2/2/h/500"></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E5%85%AD%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(1)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/%E3%80%8AEffective%20Objective-C%202.0%E3%80%8B%E7%AC%AC%E5%85%AD%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0(1)/" class="post-title-link" itemprop="url">《Effective Objective-C 2.0》第六章阅读笔记(1)</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-15 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-15T00:00:00+08:00">2018-05-15</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:19:43" itemprop="dateModified" datetime="2018-07-01T16:19:43+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="第六章：块与大中枢派发-1"><a href="#第六章：块与大中枢派发-1" class="headerlink" title="第六章：块与大中枢派发(1)"></a><center>第六章：块与大中枢派发(1)</center></h1><h2 id="第37条：理解“块”这一概念"><a href="#第37条：理解“块”这一概念" class="headerlink" title="第37条：理解“块”这一概念"></a>第37条：理解“块”这一概念</h2><p>块与函数类似，只不过是直接定义在另一个函数里，和定义他的那个函数共享一个范围内的东西。<br>块类型的语法结构如下：</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">return_type (^block_name)(parameters)</span><br></pre></td></tr></table></figure>

<h4 id="变量捕获"><a href="#变量捕获" class="headerlink" title="变量捕获"></a>变量捕获</h4><p>block 可以捕获外部变量，例如：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> additional = <span class="number">5</span>;</span><br><span class="line"><span class="type">int</span> (^addBlock)(<span class="type">int</span> a, <span class="type">int</span> b) = ^(<span class="type">int</span> a, <span class="type">int</span> b) &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b + additional;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="type">int</span> add = addBlock(<span class="number">2</span>, <span class="number">5</span>);</span><br></pre></td></tr></table></figure>
<p>block 捕获 additional 变量，仅仅是捕获 additional 那一刻的值，捕获了之后，如果外部 additional 的值改变了，此时并不会影响 block 内部 additional 的值，因为这个值是一个常量，分别存放在两个不同的内存中，是互不干扰的。如果尝试去修改此时 block 内部的additional 变量的值，编译器会报错。<br>事实上，在 ARC 环境下，block 外部的 additional 变量是存放在栈中的，而 block 内部的 additional 变量则是存放在堆中的。<br>那么，如果需要 block 内外共享一份内存呢？这时可以给变量加上 <strong>__block</strong> 关键字。  </p>
<h4 id="block-关键字修饰变量"><a href="#block-关键字修饰变量" class="headerlink" title="__block 关键字修饰变量"></a>__block 关键字修饰变量</h4><p>下面用 __block 关键字修饰 additional 变量，那么当外部的 additional 变量改变时，里面的 additional 值也会改变。因为这两个是同一个值。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">__block <span class="type">int</span> additional = <span class="number">5</span>;</span><br><span class="line"><span class="type">int</span> (^addBlock)(<span class="type">int</span> a, <span class="type">int</span> b) = ^(<span class="type">int</span> a, <span class="type">int</span> b) &#123;</span><br><span class="line">    additional = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">return</span> a + b + additional;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="type">int</span> add = addBlock(<span class="number">2</span>, <span class="number">5</span>);</span><br></pre></td></tr></table></figure>
<p>用 __block 修饰的变量存放在堆中，和 block 中的 additional 共享同一份内存，是同一个数据。</p>
<h4 id="引用循环"><a href="#引用循环" class="headerlink" title="引用循环"></a>引用循环</h4><p>如果在 block 中引用了某个对象，比如self，而这个对象正好直接或者间接引用了 block ，那么就会造成引用循环。<br>所以一般在 block 中引用的变量都会使用弱引用。</p>
<h4 id="块的内部结构"><a href="#块的内部结构" class="headerlink" title="块的内部结构"></a>块的内部结构</h4><p>块本身也是对象，在存放块对象的内存区域中，首个变量是指向Class对象的指针，该指针叫做isa。其余内存里含有块对象正常运转所需的各种信息。下图描述了块对象的内存布局。<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-18/27886440.jpg">  </p>
<p>在内存布局中，最重要的就是invoke变量，这是个函数指针，指向块的实现代码。函数原型至少要接受一个void *型的参数，此参数代表块。</p>
<p>descriptor 变量是指向结构体的指针，每个块里都包含此结构体，其中声明了块对象的总体大小，还声明了 copy 与 dispose 这两个辅助函数所对应的函数指针。辅助函数在拷贝及丢弃块对象时运行，其中会执行一些操作，比方说，前者 copy 要保留捕获的对象，而后者 dispose 则将之释放。</p>
<p>block 会把它所捕获的所有变量都拷贝一份，拷贝的是指向这些对象的指针变量。invoke函数为何需要把块对象作为参数传进来呢？原因就在于，执行块时，要从内存中把这些捕获到的变量读出来。</p>
<h4 id="全局块、栈块及堆块"><a href="#全局块、栈块及堆块" class="headerlink" title="全局块、栈块及堆块"></a>全局块、栈块及堆块</h4><p>定义块时，其所占的内存区域是分配在栈中的。这就是说，块只在定义他的那个范围内有效。例如，下面这段代码会有问题：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> (^block)();</span><br><span class="line"><span class="keyword">if</span> ( <span class="comment">/* ... */</span> ) &#123;</span><br><span class="line">    block = ^&#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;Block A&quot;</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    block = ^&#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;Block B&quot;</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line">block();</span><br></pre></td></tr></table></figure>
<p>上面两个 block 都是分配在栈中的，当离开了作用域后，就会将其释放掉，也就是两个 block 只在 if else 内有效。所以离开了 if slse 后在执行 block的话就可能会出问题。若编译器未覆写待执行的 block，则程序照常运行，若覆写，则程序崩溃。</p>
<p><strong>其实这就是为什么 block 属性要使用 copy 修饰的原因。</strong>给 block 发送 copy 消息将其拷贝。这样就可以把 block 从栈复制到堆了。拷贝后的 block，可以在定义它的范围之外使用。而且，一旦复制到堆上，块就成了带引用计数的对象了。后续的复制操作都不会真的执行复制，只是递增对象的引用计数。 </p>
<p>给上面的 block 发送 copy 消息就可以保证程序可以正确运行   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> (^block)();</span><br><span class="line"><span class="keyword">if</span> ( <span class="comment">/* ... */</span> ) &#123;</span><br><span class="line">    block = [^&#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;Block A&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">copy</span>];</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    block = [^&#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;Block B&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">copy</span>];</span><br><span class="line">&#125;</span><br><span class="line">block();</span><br></pre></td></tr></table></figure>
<p>此时的 block 是分配到堆的，这样在 if else 外也可以使用。</p>
<h4 id="全局块"><a href="#全局块" class="headerlink" title="全局块"></a>全局块</h4><p>这种块不会捕捉任何状态（比如外围的变量等），运行时也无须有状态来参与。块所使用的整个内存区域，在编译期已经完全确定了，因此，全局块可以声明在全局内存里，而不需要在每次用到的时候于栈中创建。另外，全局块的拷贝操作是个空操作，因为全局块绝不可能为系统所回收。这种块实际上相当于单例。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> (^block)() = ^&#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;This is a block&quot;</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>此 block 所需的全部信息都能在编译期确定，所以可把它做成全局块。</p>
<h3 id="要点"><a href="#要点" class="headerlink" title="要点"></a>要点</h3><p>块可以分配在栈、堆或者全局上。分配在栈上的块可以拷贝到堆里，就和标准的 Objective-C 对象一样具备了引用计数。</p>
<h2 id="第38条：为常用的块类型创建typedef"><a href="#第38条：为常用的块类型创建typedef" class="headerlink" title="第38条：为常用的块类型创建typedef"></a>第38条：为常用的块类型创建typedef</h2><p>一开始我们定义 block 是这样的</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> (^variableName)(<span class="type">BOOL</span> flag, <span class="type">int</span> value) = ^(<span class="type">BOOL</span> flag, <span class="type">int</span> value) &#123;</span><br><span class="line">    <span class="keyword">return</span> someInt;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>这样做会有两个不友好的问题 </p>
<h4 id="不易读"><a href="#不易读" class="headerlink" title="不易读"></a>不易读</h4><p>如果我们提供的接口中有好几个 block ，每个 block 中又有好几个参数，这样会感觉比较难读。<br>解决方法是给 block 类型定义一个别名  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> (^EOCSomeBlock)(<span class="type">BOOL</span> flag, <span class="type">int</span> value);</span><br><span class="line"></span><br><span class="line">EOCSomeBlock block = ^(<span class="type">BOOL</span> flag, <span class="type">int</span> value) &#123;</span><br><span class="line">    <span class="keyword">return</span> someInt;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>这样使用起来就会简介很多。</p>
<h4 id="不易修改"><a href="#不易修改" class="headerlink" title="不易修改"></a>不易修改</h4><p>当打算重构 block 的类型签名时，比方说，要给原来的 completion handler block 再加一个参数，如果没有使用别名的话，那么我们需要将所有使用了该 block 的地方都修改，这样显得过于繁杂。如果使用了别名的话，那么只需修改类型定义语句即可。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>当要在多个地方使用同种签名的 block 时，应该给该 block 定义一个别名，然后在需要的地方使用该别名定义 block 。</p>
<h2 id="第39条：用-handler-块降低代码分散程度"><a href="#第39条：用-handler-块降低代码分散程度" class="headerlink" title="第39条：用 handler 块降低代码分散程度"></a>第39条：用 handler 块降低代码分散程度</h2><p>程序在执行任务时，通常需要 “异步执行” ，这样做的好处在于：处理用户界面的显示及触摸操作所用的线程，不会因为要执行I&#x2F;O或网络通信这类耗时的任务而阻塞。某些情况下，如果应用程序在一定时间内无响应，那么就会自动终止。“系统监控器”（system watchdog）在发现某个应用程序的主线程已经阻塞了一段时间之后，就会令其终止。  </p>
<p>通常有两种方式可以处理异步代码  </p>
<h3 id="delegate"><a href="#delegate" class="headerlink" title="delegate"></a>delegate</h3><p>使用 delegate 会使代码变得分散，当一个对象同时接收多个同种类型对象的委托时，还需要在委托方法中判断是哪个对象传来的委托。那么代码会变的更加复杂。delegate 一般用在一个委托对象有多个委托事件的情况下，比如：UITableView，其他情况可以使用 block 来实现。</p>
<h3 id="block"><a href="#block" class="headerlink" title="block"></a>block</h3><p>用 block 处理起来代码会变的更加清晰。block 可以令这种API变得更紧凑，同时也令开发者调用起来更加方便。</p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)vch_successWithComplete:(VCHAddNewDeviceComplete)complete failure:(VCHFailure)failure &#123;</span><br><span class="line">    [<span class="keyword">self</span> vch_startWithComplete:^(<span class="type">id</span> object) &#123;</span><br><span class="line">        <span class="comment">// do something</span></span><br><span class="line">        complete();</span><br><span class="line">    &#125; failure:^(<span class="built_in">NSString</span> *error) &#123;</span><br><span class="line">        <span class="comment">// do something</span></span><br><span class="line">        failure(error);</span><br><span class="line">    &#125;];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里我的处理方式是将成功和失败分开处理，也可以用一个 block 来处理两个两种情况，两种方法均有优劣。具体可多看看官方的做法。</p>
<h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>在创建对象时，可以使用内联的handler块将相关业务逻辑一并声明。使代码变得更加紧凑。  </p>
<h2 id="第40条：用-block-引用其所属对象时不要出现引用循环"><a href="#第40条：用-block-引用其所属对象时不要出现引用循环" class="headerlink" title="第40条：用 block 引用其所属对象时不要出现引用循环"></a>第40条：用 block 引用其所属对象时不要出现引用循环</h2><p>书中的例子比较长，我用项目中的一部分代码来替代，意思是一样的   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">self</span>.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^&#123;</span><br><span class="line">	[<span class="keyword">self</span> queryFence];</span><br><span class="line">&#125;];</span><br><span class="line">[<span class="keyword">self</span>.tableView.mj_header beginRefreshing];</span><br></pre></td></tr></table></figure>
<p>上面的代码会出现引用循环，self -&gt; mj_header -&gt; block -&gt; self 。这个是初学时很容易犯的错误。这种情况下有两种比较常用的方法可以解决这个问题，一种就是用完 block 后，立即将其释放，另一种就是使用 __weak 关键字修饰某一环节。这里我使用第二种方法，代码如下  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">__<span class="keyword">weak</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) weakSelf = <span class="keyword">self</span>;</span><br><span class="line"><span class="keyword">self</span>.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^&#123;</span><br><span class="line">	[weakSelf queryFence];</span><br><span class="line">&#125;];</span><br><span class="line">[<span class="keyword">self</span>.tableView.mj_header beginRefreshing];</span><br></pre></td></tr></table></figure>
<p>此时 block 弱引用了 self ，这个循环也就被打破了。</p>
<h3 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h3><p>如果 block 所捕获的对象直接或间接的保留了 block 本身，那么就需要解除引用循环。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="https://vhuichen.github.io/2018/05/2018/05/iOS%E5%BC%80%E5%8F%91%E4%B9%8B%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95%EF%BC%88%E5%BE%AE%E4%BF%A1%E3%80%81QQ%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/vhuichen.jpg">
      <meta itemprop="name" content="vhuichen">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vhuichen">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | vhuichen">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2018/05/2018/05/iOS%E5%BC%80%E5%8F%91%E4%B9%8B%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95%EF%BC%88%E5%BE%AE%E4%BF%A1%E3%80%81QQ%EF%BC%89/" class="post-title-link" itemprop="url">iOS开发之第三方登录（微信、QQ）</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Posted on</span>

      <time title="Created: 2018-05-14 00:00:00" itemprop="dateCreated datePublished" datetime="2018-05-14T00:00:00+08:00">2018-05-14</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">Edited on</span>
      <time title="Modified: 2018-07-01 16:35:44" itemprop="dateModified" datetime="2018-07-01T16:35:44+08:00">2018-07-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">In</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/iOS%E5%BC%80%E5%8F%91/" itemprop="url" rel="index"><span itemprop="name">iOS开发</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="微信登录"><a href="#微信登录" class="headerlink" title="微信登录"></a>微信登录</h2><h3 id="注册-微信开放平台-账号"><a href="#注册-微信开放平台-账号" class="headerlink" title="注册 微信开放平台 账号"></a>注册 微信开放平台 账号</h3><p>地址：<a target="_blank" rel="noopener" href="https://open.weixin.qq.com/">微信开放平台</a><br>随后创建一个应用，提交给微信审核（微信审核比较慢，目前已经几天还在审核中）。</p>
<h3 id="开发者资质认证"><a href="#开发者资质认证" class="headerlink" title="开发者资质认证"></a>开发者资质认证</h3><p>微信的原话是这样的</p>
<blockquote>
<p>微信开放平台帐号的开发者资质认证提供更安全、更严格的真实性认证、也能够更好的保护企业及用户的合法权益<br>开发者资质认证通过后，微信开放平台帐号下的应用，将获得微信登录、智能接口、第三方平台开发等高级能力<br>认证有效期：一年，有效期最后三个月可申请年审即可续期<br>审核费用：中国大陆地区：300元，非中国大陆地区：99美元  </p>
</blockquote>
<p>这里要注意的是，文档里面却写着不需要收费  </p>
<blockquote>
<p>3.开放平台移动应用微信登陆目前是否收费？<br>答：“微信登录”和第三方网站共享微信庞大的用户价值，同时为微信用户提供更便捷服务和更优质内容，实现双向共赢，目前不收取任何费用。</p>
</blockquote>
<p>总之，需要先交钱认证。认证的时候还要提供 N 多资料。审核有点繁琐，写错了就要重填，有3次免费修改的机会。</p>
<h3 id="集成SDK"><a href="#集成SDK" class="headerlink" title="集成SDK"></a>集成SDK</h3><p>直接通过 CocoaPods 集成   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod <span class="string">&quot;WechatOpenSDK&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="配置工程文件"><a href="#配置工程文件" class="headerlink" title="配置工程文件"></a>配置工程文件</h3><p>这里要设置白名单以及 URL Scheme 。微信 的 URL Scheme 就是 APPID<br><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-21/39395880.jpg"></p>
<h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>这里我自定义了一个 WXLoginHelper 类，将所有的处理逻辑都放在里面。<br>我们需要先重写 application 的两个委托，具体如下：    </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&lt;WXApi.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="string">&quot;WXLoginHelper.h&quot;</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application didFinishLaunchingWithOptions:(<span class="built_in">NSDictionary</span> *)launchOptions &#123;</span><br><span class="line">    <span class="comment">// Override point for customization after application launch.</span></span><br><span class="line">    [WXLoginHelper registerApp:kWXAppID appSecret:kWXAppSecret];</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ios9</span></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)app openURL:(<span class="built_in">NSURL</span> *)url options:(<span class="built_in">NSDictionary</span>&lt;<span class="built_in">UIApplicationOpenURLOptionsKey</span>,<span class="type">id</span>&gt; *)options &#123;</span><br><span class="line">     <span class="keyword">return</span> [WXLoginHelper handleOpenURL:url];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ios8</span></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application openURL:(<span class="built_in">NSURL</span> *)url sourceApplication:(<span class="built_in">NSString</span> *)sourceApplication annotation:(<span class="type">id</span>)annotation &#123;</span><br><span class="line">    <span class="keyword">return</span> [WXLoginHelper handleOpenURL:url];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后在 WXLoginHelper 中实现 WXApiDelegate 委托，有两个方法，目前只需要关注 onResp 方法即可  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&quot;WXLoginHelper.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">WXLoginHelper</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)onResp:(BaseResp *)resp &#123;</span><br><span class="line">    SendAuthResp *sendAuthResq = (SendAuthResp *)resp;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;resp.code = %@&quot;</span>, sendAuthResq.code);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!sendAuthResq.errCode) &#123;</span><br><span class="line">        <span class="built_in">NSString</span> *urlString = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@&quot;https://api.weixin.qq.com/sns/oauth2/access_token?appid=%@&amp;secret=%@&amp;code=%@&amp;grant_type=authorization_code&quot;</span>, <span class="keyword">self</span>.appId, <span class="keyword">self</span>.appSecret, sendAuthResq.code];</span><br><span class="line">        <span class="type">id</span> json = [<span class="keyword">self</span> getDataWithURLString:urlString];</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;json : %@&quot;</span>, json);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (json) &#123;</span><br><span class="line">            <span class="built_in">NSString</span> *access_token = json[<span class="string">@&quot;access_token&quot;</span>];</span><br><span class="line">            <span class="built_in">NSString</span> *openid = json[<span class="string">@&quot;openid&quot;</span>];</span><br><span class="line">            </span><br><span class="line">            <span class="built_in">NSString</span> *urlString = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@&quot;https://api.weixin.qq.com/sns/userinfo?access_token=%@&amp;openid=%@&quot;</span>, access_token, openid];</span><br><span class="line">            <span class="type">id</span> json1 = [<span class="keyword">self</span> getDataWithURLString:urlString];</span><br><span class="line">            </span><br><span class="line">            <span class="built_in">NSLog</span>(<span class="string">@&quot;json1 : %@&quot;</span>, json1);</span><br><span class="line">            <span class="keyword">if</span> (json1) &#123;</span><br><span class="line">                <span class="built_in">NSString</span> *icon = json1[<span class="string">@&quot;headimgurl&quot;</span>];</span><br><span class="line">                <span class="built_in">NSString</span> *name = json1[<span class="string">@&quot;nickname&quot;</span>];</span><br><span class="line">                <span class="keyword">self</span>.callback ? <span class="keyword">self</span>.callback(openid, icon, name) : <span class="literal">nil</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">self</span>.callback = <span class="literal">nil</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>

<p>这里的 callback block 会将 openid，icon，name，返回给调用者。  </p>
<blockquote>
<p>需要注意的是不要直接将从微信拿到的图片链接传给服务器，因为这个链接可能会失效。我的处理方式是，从这个链接拿到图片，然后将图片传给服务器。</p>
</blockquote>
<p>最后在点击了微信登录按钮后给微信发送消息   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">+ (<span class="type">void</span>)sendRepWithCallback:(<span class="type">void</span> (^)(<span class="built_in">NSString</span> *, <span class="built_in">NSString</span> *, <span class="built_in">NSString</span> *))callback &#123;</span><br><span class="line">    [WXLoginHelper sharedInstance].callback = callback;</span><br><span class="line">    </span><br><span class="line">    SendAuthReq *req = [[SendAuthReq alloc] init];</span><br><span class="line">    req.scope = <span class="string">@&quot;snsapi_userinfo&quot;</span>;</span><br><span class="line">    req.state = <span class="string">@&quot;text&quot;</span>;</span><br><span class="line">    [WXApi sendReq:req];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>用户点击授权或者取消都会通过 onResp 方法告诉调用者授权状态。</p>
<h2 id="QQ登录"><a href="#QQ登录" class="headerlink" title="QQ登录"></a>QQ登录</h2><h3 id="注册-腾讯开发者平台-账号"><a href="#注册-腾讯开发者平台-账号" class="headerlink" title="注册 腾讯开发者平台 账号"></a>注册 腾讯开发者平台 账号</h3><p>地址：<a target="_blank" rel="noopener" href="http://open.qq.com/">腾讯开发者平台</a>  </p>
<p>创建一个应用并提交审核（QQ审核很快，当天就通过了）。  </p>
<blockquote>
<p>和微信不一样，QQ登录目前不需要付费</p>
</blockquote>
<h3 id="集成SDK-1"><a href="#集成SDK-1" class="headerlink" title="集成SDK"></a>集成SDK</h3><h4 id="下载-SDK"><a href="#下载-SDK" class="headerlink" title="下载 SDK"></a>下载 SDK</h4><p>找了好久都没发现可以通过 CocoaPods 集成，所以只能手动。<a target="_blank" rel="noopener" href="http://wiki.connect.qq.com/sdk%e4%b8%8b%e8%bd%bd">下载地址</a><br>下载好的SDK里面包含：环境搭建、API说明、Demo、framework 四个文件。  </p>
<p><img src="http://ovsbvt5li.bkt.clouddn.com/18-5-21/16746928.jpg"></p>
<h4 id="集成"><a href="#集成" class="headerlink" title="集成"></a>集成</h4><p>将 TencentOpenAPI.framework 加到项目，然后添加依赖库 “SystemConfiguration.framework”。</p>
<h3 id="配置工程文件-1"><a href="#配置工程文件-1" class="headerlink" title="配置工程文件"></a>配置工程文件</h3><p>工程配置和微信一样，可以看上图。这里稍微有点区别的就是 QQ 的 URL Scheme &#x3D; tencent + appid 。  </p>
<h3 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h3><p>代码逻辑跟微信差不多。这里我创建了 QQLoginHelper 类用来处理相关的逻辑，遵守 TencentSessionDelegate 这个协议，主要看协议里面的这个两个方法  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="type">void</span>)tencentDidLogin &#123;</span><br><span class="line">    <span class="keyword">if</span> (_tencentOAuth.accessToken) &#123;</span><br><span class="line">        <span class="comment">// 回调方法：- (void)getUserInfoResponse:(APIResponse *) response</span></span><br><span class="line">        [_tencentOAuth getUserInfo];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;accessToken 获取失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)getUserInfoResponse:(APIResponse *)response &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;response ：%@&quot;</span>,response.jsonResponse);</span><br><span class="line">    <span class="keyword">if</span> (response.jsonResponse &amp;&amp; <span class="keyword">self</span>.callback) &#123;</span><br><span class="line">        <span class="keyword">self</span>.callback(_tencentOAuth.openId, response.jsonResponse[<span class="string">@&quot;figureurl_qq_2&quot;</span>], response.jsonResponse[<span class="string">@&quot;nickname&quot;</span>]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">self</span>.callback = <span class="literal">nil</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>登录成功会调用 tencentDidLogin 方法，然后在方法中调用 getUserInfo 方法获取用户信息。最后会通过 block 将获取到的信息返回。  </p>
<p>点击QQ登录，通过下面的代码给QQ发送消息，调用方式如下   </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">+ (<span class="type">void</span>)sendRepWithCallback:(<span class="type">void</span> (^)(<span class="built_in">NSString</span> *, <span class="built_in">NSString</span> *, <span class="built_in">NSString</span> *))callback &#123;</span><br><span class="line">    QQLoginHelper *helper = [QQLoginHelper sharedInstance];</span><br><span class="line">    helper.callback = callback;</span><br><span class="line">    </span><br><span class="line">    helper-&gt;_tencentOAuth = [[TencentOAuth alloc] initWithAppId:helper.appId andDelegate:helper];</span><br><span class="line">    helper-&gt;_permissionArray = [<span class="built_in">NSMutableArray</span> arrayWithObjects: kOPEN_PERMISSION_GET_SIMPLE_USER_INFO,kOPEN_PERMISSION_GET_INFO,kOPEN_PERMISSION_GET_USER_INFO,<span class="literal">nil</span>];</span><br><span class="line">    [helper-&gt;_tencentOAuth authorize:helper-&gt;_permissionArray inSafari:<span class="literal">NO</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>Demo 中我用 block 回调获取到的信息，而我是使用单例处理的，所以 block 中的对象不能被强引用，虽然我调用完 block 后将其释放掉，但有时候回调函数可能并没有执行，那么这时就可能会出现内存泄漏。所以最好的方法是对 block 中的对象使用弱引用。代码如下：  </p>
<figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="keyword">IBAction</span>)wechatClick:(<span class="type">id</span>)sender &#123;</span><br><span class="line">    <span class="keyword">if</span> ([WXLoginHelper isWXAppInstalled]) &#123;</span><br><span class="line">        <span class="comment">// 使用 __weak 可以处理特殊情况下内存泄漏的问题，比如跳转到微信后没有再跳转回来</span></span><br><span class="line">        __<span class="keyword">weak</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) weakSelf = <span class="keyword">self</span>;</span><br><span class="line">        [WXLoginHelper sendRepWithCallback:^(<span class="built_in">NSString</span> *openid, <span class="built_in">NSString</span> *icon, <span class="built_in">NSString</span> *name) &#123;</span><br><span class="line">            __<span class="keyword">strong</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) strongSelf = weakSelf;</span><br><span class="line">            [strongSelf thirdPartLoginOpenId:openid userIcon:icon userName:name type:<span class="number">1</span>];</span><br><span class="line">        &#125;];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;没有安装微信客户端&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">IBAction</span>)qqClick:(<span class="type">id</span>)sender &#123;</span><br><span class="line">    <span class="keyword">if</span> ([QQLoginHelper isQQInstalled]) &#123;</span><br><span class="line">        <span class="comment">// 使用 __weak 可以处理特殊情况下内存泄漏的问题，比如跳转到QQ后没有再跳转回来</span></span><br><span class="line">        __<span class="keyword">weak</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) weakSelf = <span class="keyword">self</span>;</span><br><span class="line">        [QQLoginHelper sendRepWithCallback:^(<span class="built_in">NSString</span> *openid, <span class="built_in">NSString</span> *icon, <span class="built_in">NSString</span> *name) &#123;</span><br><span class="line">            __<span class="keyword">strong</span> <span class="keyword">typeof</span>(<span class="keyword">self</span>) strongSelf = weakSelf;</span><br><span class="line">            [strongSelf thirdPartLoginOpenId:openid userIcon:icon userName:name type:<span class="number">2</span>];</span><br><span class="line">        &#125;];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;没有安装QQ客户端&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h4><p><strong>1、同一个QQ账号，iOS、Android 手机登录获取到的 OpenID 不相同。</strong><br>原因：创建的应用必须拥有相同的 APPID。</p>
<h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><p><a target="_blank" rel="noopener" href="https://github.com/vhuichen/WechatQQLogin">Demo</a> 有在不断的优化，部分代码可能会和上面的不一样。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/page/4/"><i class="fa fa-angle-left" aria-label="Previous page"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/4/">4</a><span class="page-number current">5</span><a class="page-number" href="/page/6/">6</a><span class="space">&hellip;</span><a class="page-number" href="/page/10/">10</a><a class="extend next" rel="next" href="/page/6/"><i class="fa fa-angle-right" aria-label="Next page"></i></a>
  </nav>

</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 2015 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">vhuichen</span>
</div>
  <div class="powered-by">Powered by <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/pisces/" rel="noopener" target="_blank">NexT.Pisces</a>
  </div>

    </div>
  </footer>

  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/next-boot.js"></script>

  





  





</body>
</html>
